PHP基础安全开发
环境搭建
phpstorm
PHPStorm 是由 JetBrains 开发的一款集成开发环境 (IDE),专门为 PHP 开发设计。以下是 PHPStorm 的一些关键特点和优势:
- 智能编码辅助:PHPStorm 提供代码补全、错误检测、重构和导航功能,可以显著提升开发效率。
- 调试和测试:内置强大的调试工具,支持 Xdebug 和 Zend Debugger,方便开发者进行代码调试。还集成了 PHPUnit 进行单元测试。
- 代码质量工具:集成了代码质量检查工具,如 PHP_CodeSniffer、PHP Mess Detector 和 PHP CS Fixer,有助于保持代码整洁和符合规范。
- 支持多种框架:支持主流 PHP 框架,如 Laravel、Symfony、Zend Framework、Yii 等,提供相应的框架辅助功能。
- 前端开发支持:内置 HTML、CSS 和 JavaScript 的支持,包括 Sass、Less、TypeScript 和 CoffeeScript,适合全栈开发。
- 数据库和 SQL 支持:提供强大的数据库工具,可以直接在 IDE 中管理和查询数据库。
- 版本控制集成:支持 Git、SVN、Mercurial 等版本控制系统,方便代码管理和协作。
- 插件扩展:支持安装各种插件,进一步扩展其功能,满足个性化需求。
下载链接
https://www.jetbrains.com/zh-cn/phpstorm/download/#section=windows
配置本地PHP解释器
PHPStorm 本身并不包含 PHP 解释器,它只是一个 IDE,提供开发和调试功能。即使你已经下载并安装了 PHPStorm,它仍然需要你配置 PHP 解释器,因为 PHPStorm 需要知道哪个 PHP 解释器用来运行和调试 PHP 代码。
官方文档
我选择下载XAMPP(XAMPP 是一个免费开源的跨平台 Web 服务器解决方案,主要用于开发和测试 Web 应用程序。)
//和PHPstudy功能类似
XAMPP下载链接
手搓评论系统
首先先让chatGPT生成一个评论系统的表单
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>评论页面</title>
<style>
body {
font-family: Arial, sans-serif;
}
.comment-form {
max-width: 500px;
margin: 0 auto;
padding: 1em;
background: #f9f9f9;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.comment-form h2 {
margin-bottom: 1em;
}
.comment-form label {
display: block;
margin-bottom: 0.5em;
}
.comment-form input,
.comment-form textarea {
width: 100%;
padding: 0.5em;
margin-bottom: 1em;
border: 1px solid #ccc;
border-radius: 5px;
}
.comment-form button {
padding: 0.5em 2em;
background: #007BFF;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
}
.comment-form button:hover {
background: #0056b3;
}
</style>
</head>
<body>
<div class="comment-form">
<h2>发表评论</h2>
<form action="" method="post">//注意此处的action,这里指的是将数据发送到哪一个文件,留空指的是发送给自身文件
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="content">评论内容:</label>
<textarea id="content" name="content" rows="5" required></textarea>
<button type="submit">提交评论</button>
</form>
</div>
</body>
</html>
数据库连接 mysqli_connect
$dbip='localhost';
$dbuser='root';
$dbpass='root';
$dbname='kongyu';
$con=mysqli_connect($dbip,$dbuser,$dbpass,$dbname);
// 检查连接
if (!$con)
{
die("连接错误: " . mysqli_connect_error());
}
else
{
echo "ok";
}
其中关于mysqli的函数使用见:
https://www.runoob.com/php/php-ref-mysqli.html
常用:
- mysqli_connect() 打开一个到MySQL的新的连接。
- mysqli_select_db() 更改连接的默认数据库。
- mysqli_query() 执行某个针对数据库的查询。
- mysqli_fetch_row() 从结果集中取得一行,并作为枚举数组返回。
- mysqli_close() 关闭先前打开的数据库连接。
超全局变量
$GLOBALS:这种全局变量用于在 PHP 脚本中的任意位置访问全局变量
$_SERVER:这种超全局变量保存关于报头、路径和脚本位置的信息。
$_REQUEST:$_REQUEST 用于收集 HTML 表单提交的数据。
$_POST:广泛用于收集提交method="post" 的HTML表单后的表单数据。
$_GET:收集URL中的发送的数据。也可用于收集提交HTML表单数据(method="get")
$_FILES:文件上传且处理包含通过HTTP POST方法上传给当前脚本的文件内容。
$_ENV:是一个包含服务器端环境变量的数组。
$_COOKIE:是一个关联数组,包含通过cookie传递给当前脚本的内容。
$_SESSION:是一个关联数组,包含当前脚本中的所有session内容。
参考文章:
PHP 全局变量 - 超全局变量
超全局变量
执行简单数据库操作-插入数据
首先数据库操作语句简单整理如下:
查:select * from 表名 where 列名='条件';
增:insert into 表名(`列名1`, `列名2`) value('列1值1', '列2值2');
删:delete from 表名 where 列名 = '条件';
改:update 表名 set 列名 = 数据 where 列名 = '条件';
if (isset($username)) {
$sql = "insert into comment(`username`, `content`,`ip`,`uagent`) value('$username','$content','$ip','$uagent');";
// 执行SQL查询
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";
} else {
echo "错误: " . mysqli_error($con);
}
}
这里就是先构造了个字符串
$con
指定数据库连接$sql
指定数据库操作语句
其中isset($username)
确保username中有值才会执行
简单的删除评论
<?php
include '../config.php';
$delstr=@$_GET['del'];
if(isset($delstr)){
$sql2="delete from comment where username = '$delstr';";
if(mysqli_query($con,$sql2)){
echo "<script>alert('删除成功!')</script>";
}
}
$sql1="select * from comment";
$data=mysqli_query($con,$sql1);
while ($row=mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:'.$row[0].'<br>';
echo '内容:'.$row[1].'<br>';
echo 'IP地址:'.$row[2].'<br>';
echo 'UA浏览器:'.$row[3].'<br>';
echo "<a href='comment-admin.php?del=$row[0]'>删除</a>";
}
这部分关键代码也就是
echo "<a href='comment-admin.php?del=$row[0]'>删除</a>";
这个会输出一个按钮,点击就会访问指定的路径,也就是comment-admin.php?del=$row[0]
下面利用全局变量接收del来进行删除
这样的逻辑漏洞就显而易见,也就是如果直接构造一个url也可以实现删除操作
http://localhost:63342/kongyu1/admin/comment-admin.php?del=555
进行简单的函数封装
<?php
include "config.php";
//添加评论
function add_comment($con){
$username=@$_POST['username'];
$content=@$_POST['content'];
$ip = @$_SERVER['REMOTE_ADDR'];
$uagent = @$_SERVER['HTTP_USER_AGENT'];
if (isset($username)) {
$sql = "insert into comment(`username`, `content`,`ip`,`uagent`) value('$username','$content','$ip','$uagent');";
// 执行SQL查询
if (mysqli_query($con, $sql)) {
echo "<script>alert('留言成功!')</script>";
} else {
echo "错误: " . mysqli_error($con);
}
}
}
//显示评论
function show_comment($con,$del){
$sql1="select * from comment";
$data=mysqli_query($con,$sql1);
while ($row=mysqli_fetch_row($data)) {
echo '<hr>';
echo '用户名:'.$row[0].'<br>';
echo '内容:'.$row[1].'<br>';
echo 'IP地址:'.$row[2].'<br>';
echo 'UA浏览器:'.$row[3].'<br>';
if($del=='del'){
echo "<a href='gbook-admin.php?del=$row[0]'>删除</a>";
}
}
}
if (!$con) {
die("连接错误: " . mysqli_connect_error());
} else {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
add_comment($con);
}
show_comment($con, 'x');
}
进一步优化
- Prepared Statements:使用预处理语句来防止SQL注入攻击。
- NULL Coalescing Operator:使用
??
运算符来提供默认值。 - Input Validation:确保
username
和content
都存在且不为空。 - Error Handling:在添加评论时提供更详细的错误信息。
- HTML Entities:在输出评论内容时使用
htmlspecialchars
函数来防止XSS攻击。 - Line Breaks:在显示评论内容时使用
nl2br
来保持内容中的换行符。
<?php
include "config.php";
// 添加评论
function add_comment($con) {
$username = $_POST['username'] ?? null;
$content = $_POST['content'] ?? null;
$ip = $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
$uagent = $_SERVER['HTTP_USER_AGENT'] ?? 'Unknown';
if ($username && $content) {
$stmt = $con->prepare("INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)");
$stmt->bind_param('ssss', $username, $content, $ip, $uagent);
if ($stmt->execute()) {
echo "<script>alert('留言成功!')</script>";
} else {
echo "错误: " . $con->error;
}
$stmt->close();
} else {
echo "<script>alert('用户名和内容不能为空!')</script>";
}
}
// 显示评论
function show_comment($con, $del) {
$sql = "SELECT * FROM comment";
$result = $con->query($sql);
if ($result->num_rows > 0) {
while ($row = $result->fetch_assoc()) {
echo '<hr>';
echo '用户名:' . htmlspecialchars($row['username'], ENT_QUOTES, 'UTF-8') . '<br>';
echo '内容:' . nl2br(htmlspecialchars($row['content'], ENT_QUOTES, 'UTF-8')) . '<br>';
echo 'IP地址:' . htmlspecialchars($row['ip'], ENT_QUOTES, 'UTF-8') . '<br>';
echo 'UA浏览器:' . htmlspecialchars($row['uagent'], ENT_QUOTES, 'UTF-8') . '<br>';
if ($del === 'del') {
echo "<a href='gbook-admin.php?del=" . urlencode($row['username']) . "'>删除</a>";
}
}
} else {
echo "没有评论";
}
}
if (!$con) {
die("连接错误: " . mysqli_connect_error());
} else {
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
add_comment($con);
}
show_comment($con, 'x');
}
?>
sql预处理语句
预处理语句(Prepared Statements)是数据库查询的一种方法,通过将查询模板和数据分离来提高安全性和性能。它们是防止SQL注入攻击的重要工具。
如何工作
-
准备查询模板:首先,数据库服务器将包含参数占位符的SQL查询模板发送到数据库。例如:
INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)
-
编译查询:数据库服务器预编译这个查询模板并准备好执行。
-
绑定参数:应用程序将实际的值绑定到这些参数占位符。比如,在PHP中使用
bind_param
方法:$stmt = $con->prepare("INSERT INTO comment (`username`, `content`, `ip`, `uagent`) VALUES (?, ?, ?, ?)"); $stmt->bind_param('ssss', $username, $content, $ip, $uagent);
-
执行查询:数据库服务器使用绑定的参数执行预编译的查询。
防止SQL注入攻击
SQL注入攻击通常发生在用户输入被直接拼接到SQL查询字符串中时,例如:
$username = $_POST['username'];
$sql = "SELECT * FROM users WHERE username = '$username'";
攻击者可以通过在输入中插入恶意SQL代码来改变查询的行为,例如:
' OR '1'='1
结果查询变成:
SELECT * FROM users WHERE username = '' OR '1'='1'
这将返回所有用户数据,因为 OR '1'='1'
永远为真。
通过使用预处理语句,查询和数据被分离,用户输入被视为纯粹的数据,不会被解析为SQL代码:
$username = $_POST['username'];
$stmt = $con->prepare("SELECT * FROM users WHERE username = ?");
$stmt->bind_param('s', $username);
这里,$username
的内容永远不会直接插入到SQL查询字符串中,因此即使包含恶意代码也无济于事。
优点
- 安全性:有效防止SQL注入攻击。
- 性能:对于重复执行的相同查询模板,数据库服务器只需编译一次,提高了执行效率。
- 代码清晰:查询模板和数据分离,代码更清晰易读。
使用第三方插件ueditor
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>评论页面</title>
<style> body {
font-family: Arial, sans-serif;
}
.comment-form {
max-width: 500px;
margin: 0 auto;
padding: 1em;
background: #f9f9f9;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
.comment-form h2 {
margin-bottom: 1em;
}
.comment-form label {
display: block;
margin-bottom: 0.5em;
}
.comment-form input,
.comment-form textarea {
width: 100%;
padding: 0.5em;
margin-bottom: 1em;
border: 1px solid #ccc;
border-radius: 5px;
}
.comment-form button {
padding: 0.5em 2em;
background: #007BFF;
border: none;
color: white;
border-radius: 5px;
cursor: pointer;
}
.comment-form button:hover {
background: #0056b3;
}
</style>
<!-- 引入UEditor配置文件 -->
<script type="text/javascript" src="/ueditor/ueditor.config.js"></script>
<!-- 引入UEditor核心文件 -->
<script type="text/javascript" src="/ueditor/ueditor.all.min.js"></script>
</head>
<body>
<div class="comment-form">
<h2>发表评论</h2>
<form action="" method="post">
<label for="username">用户名:</label>
<input type="text" id="username" name="username" required>
<label for="content">评论内容:</label>
<!-- 将textarea替换为UEditor -->
<script id="content" name="content" type="text/plain" style="width:100%;height:300px;"></script>
<button type="submit">提交评论</button>
</form></div>
<!-- 初始化UEditor -->
<script type="text/javascript">
var ue = UE.getEditor('content');
</script>
</body>
</html>